package hector.notifier;

import javax.imageio.ImageIO;
import javax.swing._;
import java.util.concurrent._;
import java.io._;
import java.util.Random;
import scala.collection._;
import sbinary._;
import sbinary.Instances._;
import com.trolltech.qt.gui._;
import com.trolltech.qt.core._;
import java.util.prefs._;
import sbinary.Operations._;

object Util{
  implicit def addActions (qm : QMenu) = new {
    def addAction(name : String, action : () => Unit){
      qm.addAction(name, action, "apply()");
    }
  }

  implicit val MessageGroupIsBinary : Binary[MessageGroup] = sbinary.generic.Generic.asProduct3(new MessageGroup(_ : String, _ : Long, _ : List[String]))( x => (x.name, x.delay, x.messages))
  implicit def listItem (name : String) = new QListWidgetItem(name); 
  def trace[T](t : T) : T = {println(t); t}
}

import Util._;

class MessageGroupEditor(mgs : List[MessageGroup]) extends QDialog{
  val editor = new Ui_MessageGroupEditor();
  editor.setupUi(this);
 
  import editor._;

  val localCopy = new mutable.HashMap[String, (String, String)]

  def addNewItem(x : MessageGroup) {
    val item = new QListWidgetItem(x.name);
    messageGroups.addItem(item)
    messageGroups.setCurrentItem(item)
    messageGroups.currentItemChanged.connect(new {
      def apply(current : QListWidgetItem, previous : QListWidgetItem) = {
      if (current == item){
        if (previous != null) saveTo(previous.text); 
        loadFrom(x.name)
      }   
    }}, "apply(com.trolltech.qt.gui.QListWidgetItem, com.trolltech.qt.gui.QListWidgetItem)")
    localCopy.put(x.name, ((x.delay / 60000).toString, x.messages.mkString("\n")))
    loadFrom(x.name);
  }

  mgs.foreach(addNewItem(_))

  removeItem.clicked.connect(() => {
    val groupItem = messageGroups.currentItem;
    if (groupItem != null){
      localCopy -= groupItem.text;
      messageGroups.takeItem(messageGroups.currentRow());
    }
  }, "apply()"); 

  addItem.clicked.connect(() => {
    val name = QInputDialog.getText(this, "Group name", "What do you want to call this group?");
    if (name != null){
      addNewItem(new MessageGroup(name, 0, List()));
    }
  }, "apply()");

  def loadFrom(name : String) = {
    val (delay, text) = localCopy(name);

    messages.setText(text);
    frequency.setText(delay)
  }

  def saveTo(name : String) = {
    if (localCopy.keys.contains(name)) localCopy(name) = ((frequency.text, messages.toPlainText));
  }

  def toGroups : List[MessageGroup] = {
    saveTo(messageGroups.currentItem.text)
    for ( (name, (delay, messageText)) <- localCopy.toList if (name.length > 0)) yield new MessageGroup(name,
        (try { java.lang.Long.parseLong(delay)} 
        catch { case (_ : Exception) => 5L }) * 60000 , messageText.split('\n').toList.filter(x => x.length > 0));
  }
}

class MessageGroup (val name : String, val delay : Long, val messages : List[String]){
  override def toString = name;
}

class Hector(parent : QWidget) extends QSystemTrayIcon(parent){
   val defaultMessages = List(
    "Have you hugged your frog recently?",
    "*hect*",
    "hect!",
    "hect?",
    "*boing*",
    "*burble*",
    "*foompf*"
  );


  val groups = new UserPreference[List[MessageGroup]]("hector/notifier/SavedState", List(new MessageGroup("Default", 360000, defaultMessages)))   

  val pendingMessages = new DelayQueue[PendingMessage]

  val random = new Random();

  case class PendingMessage(messageGroup : MessageGroup) extends Delayed {
    val delay = System.currentTimeMillis() + (messageGroup.delay * (0.5 + random.nextDouble)).toLong
    def compareTo(delayed : Delayed) = java.lang.Double.compare(getDelay(TimeUnit.MILLISECONDS), delayed.getDelay(TimeUnit.MILLISECONDS))
    def getDelay(unit : TimeUnit) = unit.convert(delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
  }
 
  def schedule(mg : MessageGroup){
    val message = new PendingMessage(mg);
    pendingMessages.remove(message);
    pendingMessages.put(message); 
  }

  def reschedule () = {
    pendingMessages.clear();
    groups().foreach(schedule(_));
  }

  reschedule();
  
  val hectcon = new QIcon(new QPixmap("resources/hectcon3.png", "png"))
  setContextMenu(new QMenu());
  setIcon(hectcon);

  val editMessages = () => {
    val dialog = new MessageGroupEditor(groups())
 
    if (dialog.exec() == QDialog.DialogCode.Accepted.value()){
      import dialog.editor;
      groups() = dialog.toGroups;
      reschedule();
    }
  }  
  contextMenu.addAction("Edit messages", editMessages) 

  contextMenu.addSeparator();
  
  contextMenu.addAction("Save to file", () => { 
    val name = QFileDialog.getSaveFileName();
    
    if (name != null && name.length() > 0){
      groups.exportToFile(name);
    }
  })


  contextMenu.addAction("Load from file", () => { 
    val name = QFileDialog.getOpenFileName();
    
    if (name != null && name.length() > 0){
      groups.loadFromFile(name);
    }
  })

  contextMenu.addSeparator();
  contextMenu.addAction("Exit", () => QCoreApplication.exit());
  
  val timer = new QTimer()
  timer.setInterval(5000);
  
  timer.timeout.connect(() => {
    val s = pendingMessages.poll();
   
    s match {
      case (PendingMessage(mg)) => {
        if (!mg.messages.isEmpty){
          schedule(mg);
          showMessage("Hector says...", mg.messages(random.nextInt(mg.messages.length)))
        }
      }
      case null => ();
    }      
  }, "apply()");

  timer.start();

}

object Notifier extends Application{
  QApplication.initialize(new Array[String](0));
  QApplication.setQuitOnLastWindowClosed(false);

  val hector = new Hector(null)
  hector.show();

  QApplication.exec();
}


class UserPreference[T](name : String, default : => T)(implicit bin : Binary[T]){
  private var localCache : T = null.asInstanceOf[T];

  private val preference = Preferences.userRoot().node(name);

  def apply() : T = {
    if (localCache == null){
      var bytes = preference.getByteArray("", null);
      if (bytes == null){
        localCache = default;
      } else {
        localCache = fromByteArray[T](bytes)
      } 
    }
    localCache
  }

  def update(t : T){
    localCache = t;
    preference.putByteArray("", toByteArray[T](t));
  }

  def reset(){
    localCache = null.asInstanceOf[T];
    preference.remove("");
    preference.flush();
  }

  def exportToFile(file : File){
    toFile(apply())(file);
  }

  def loadFromFile(file : File){
    update(fromFile[T](file));
  }
} 
